
package de.justphil.tcg.tcgandroidclient.gui.gameboard;

import java.util.ArrayList;
import java.util.List;

import de.justphil.tcg.tcgandroidclient.Example;
import de.justphil.tcg.tcgandroidclient.R;
import de.justphil.tcg.tcgandroidclient.Storage;
import de.justphil.tcg.tcgandroidclient.config.Config;
import de.justphil.tcg.tcgandroidclient.content.CardData;
import de.justphil.tcg.tcgandroidclient.content.DH;
import de.justphil.tcg.tcgandroidclient.content.TCGContentAdapter;
import de.justphil.tcg.tcgandroidclient.msgdata.Info;
import de.justphil.tcg.tcgandroidclient.msgdata.Join;
import de.justphil.tcg.tcgandroidclient.msgdata.Progress;
import de.justphil.tcg.tcgandroidclient.network.SlaveMessenger;
import de.justphil.tcg.tcgandroidclient.network.websocket.WebSocketService;
import de.justphil.tcg.tcgandroidclient.network.websocket.WebSocketService.SlaveMessengerBinder;
import de.justphil.tcg.tcgandroidclient.util.TCGLog;
import android.app.Activity;
import android.app.ProgressDialog;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.database.ContentObserver;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.RemoteException;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.Window;
import android.view.WindowManager;
import android.widget.Button;
import android.widget.Toast;

public class GameBoardAct extends Activity {
	
	private class MessageHandler extends Handler {
		
		public MessageHandler(Looper looper) {
		
			super(looper);
		}
		
		@Override
		public void handleMessage(Message msg) {
		
			switch (msg.what) {
				case WebSocketService.MSG_SEND_FAILURE:
					GameBoardAct.this.handleSendFailure(msg.arg1, (Message) msg.obj);
					break;
				case WebSocketService.MSG_CONNECTED:
					GameBoardAct.this.handleConnected();
					break;
				case WebSocketService.MSG_DISCONNECTED:
					GameBoardAct.this.handleDisconnected();
					break;
				case WebSocketService.MSG_PROGRESS:
					GameBoardAct.this.handleProgress((Progress) msg.obj);
					break;
				case WebSocketService.MSG_JOINED:
					GameBoardAct.this.handleJoined(msg.arg1);
					break;
				case WebSocketService.MSG_INFO:
					GameBoardAct.this.handleInfo((Info) msg.obj);
					break;
				case WebSocketService.MSG_RESUME_RQ:
					GameBoardAct.this.handleResumeRQ();
					break;
				case WebSocketService.MSG_SELECT_RQ:
					GameBoardAct.this.handleSelectRQ(msg.arg1);
					break;
				case WebSocketService.MSG_WAIT_RQ:
					GameBoardAct.this.handleWaitRQ(msg.arg1);
					break;
				case WebSocketService.MSG_WINNER:
					GameBoardAct.this.handleWinner(msg.arg1);
					break;
				case WebSocketService.MSG_DECODING_ERROR:
					GameBoardAct.this.handleDecodingError((String) msg.obj);
					break;
				default:
					super.handleMessage(msg);
			}
		}
	}
	
	public static final String EXTRA_OTP = "otp";
	public static final String EXTRA_GAME_ID = "gameId";
	private TCGContentAdapter contentAdapter;
	private boolean boundService;
	private SlaveMessenger slaveMessenger;
	private Button resumeButton;
	private Card[] opponentCards;
	private Card[] playerCards;
	private OnClickListener[] onCardClickListeners;
	private OnClickListener onConfirmClickListener;
	private ProgressDialog waitingForConnectionDialog;
	private boolean joined;
	private boolean gameOver;
	private int selectionSlot;
	/* The Activity needs this data when it is restarted. */
	// TODO: Store this Data persistent!
	private boolean selectionRequired;
	private boolean resumeRequired;
	private ServiceConnection serviceConn = new ServiceConnection() {
		
		@Override
		public void onServiceDisconnected(ComponentName name) {
		
			boundService = false;
		}
		
		@Override
		public void onServiceConnected(ComponentName name, IBinder binder) {
		
			Messenger master = new Messenger(new MessageHandler(getMainLooper()));
			slaveMessenger = ((SlaveMessengerBinder) binder).getSlaveMessenger();
			slaveMessenger.setMaster(master);
			boundService = true;
		}
	};
	
	/** Called when the activity is first created. */
	@Override
	public void onCreate(Bundle savedInstanceState) {
	
		super.onCreate(savedInstanceState);
		this.contentAdapter = new TCGContentAdapter(this);
		requestWindowFeature(Window.FEATURE_NO_TITLE);
		getWindow().setFlags(WindowManager.LayoutParams.FLAG_FULLSCREEN,
				WindowManager.LayoutParams.FLAG_FULLSCREEN);
		setContentView(R.layout.game_board);
		initializeComponents();
	}
	
	public void handleDecodingError(String msgStr) {
	
		Toast.makeText(this, "Failed decoding Message from Server:\n" + msgStr, Toast.LENGTH_SHORT)
				.show();
	}
	
	@Override
	protected void onStart() {
	
		super.onStart();
		TCGLog.d(new Object[] {
			"Binding the WebSocketService..." });
		Intent intent = new Intent(this, WebSocketService.class);
		if (!bindService(intent, serviceConn, Context.BIND_AUTO_CREATE)) {
			TCGLog.e(new Object[] {
				"   Failed to bind the WebSocketService!" });
		}
		else {
			TCGLog.d(new Object[] {
				"   Successfully bound the WebSocketService!" });
		}
		handleDisconnected();
		if (resumeRequired) {
			activateResumeListener();
		}
		else {
			deactivateResumeListener();
		}
		if (selectionRequired) {
			activateSelectionListeners(selectionSlot);
		}
		else {
			deactivateSelectionListeners();
		}
	}
	
	@Override
	protected void onStop() {
	
		super.onStop();
		if (boundService) {
			TCGLog.d(new Object[] {
				"Unbinding the InputService..." });
			unbindService(serviceConn);
			TCGLog.d(new Object[] {
				"   Unbound the InputService." });
		}
	}
	
	private void initializeComponents() {
	
		resumeButton = ((Button) findViewById(R.id.game_board_resume));
		onConfirmClickListener = new OnClickListener() {
			
			@Override
			public void onClick(View v) {
			
				deactivateResumeListener();
				TCGLog.d(new Object[] {
					"CLICK RESUME!" });
				sendMessage(Message.obtain(null, WebSocketService.MSG_RESUME));
			}
		};
		opponentCards = new Card[Config.NUM_SLOTS_PER_PLAYER];
		playerCards = new Card[Config.NUM_SLOTS_PER_PLAYER];
		onCardClickListeners = new OnClickListener[Config.NUM_SLOTS_PER_PLAYER];
		playerCards[0] = ((Card) findViewById(R.id.game_board_card_10));
		opponentCards[0] = ((Card) findViewById(R.id.game_board_card_00));
		onCardClickListeners[0] = new OnClickListener() {
			
			@Override
			public void onClick(View v) {
			
				deactivateSelectionListeners();
				TCGLog.d(new Object[] {
					"CLICK 0!" });
				Message msg = Message.obtain(null, WebSocketService.MSG_SELECT);
				msg.arg1 = 0;
				sendMessage(msg);
			}
		};
		opponentCards[1] = ((Card) findViewById(R.id.game_board_card_01));
		playerCards[1] = ((Card) findViewById(R.id.game_board_card_11));
		onCardClickListeners[1] = new OnClickListener() {
			
			@Override
			public void onClick(View v) {
			
				deactivateSelectionListeners();
				TCGLog.d(new Object[] {
					"CLICK 1!" });
				Message msg = Message.obtain(null, WebSocketService.MSG_SELECT);
				msg.arg1 = 1;
				sendMessage(msg);
			}
		};
		opponentCards[2] = ((Card) findViewById(R.id.game_board_card_02));
		playerCards[2] = ((Card) findViewById(R.id.game_board_card_12));
		onCardClickListeners[2] = new OnClickListener() {
			
			@Override
			public void onClick(View v) {
			
				deactivateSelectionListeners();
				TCGLog.d(new Object[] {
					"CLICK 2!" });
				Message msg = Message.obtain(null, WebSocketService.MSG_SELECT);
				msg.arg1 = 2;
				sendMessage(msg);
			}
		};
		for (Card card : opponentCards) {
			card.setStatus(Card.STATUS_EMPTY);
		}
		for (Card card : playerCards) {
			card.setStatus(Card.STATUS_EMPTY);
		}
	}
	
	private void sendMessage(Message msg) {
	
		try {
			slaveMessenger.sentMessage(msg);
		}
		catch (RemoteException e) {
			TCGLog.e(new Object[] {
				"Error sending a Message: " }, e);
		}
	}
	
	private void activateSelectionListeners(int slot) {
	
		playerCards[slot].setStatus(Card.STATUS_TURN);
		for (int i = 0; i < opponentCards.length; i++) {
			opponentCards[i].setOnClickListener(onCardClickListeners[i]);
		}
	}
	
	private void activateResumeListener() {
	
		resumeButton.setOnClickListener(onConfirmClickListener);
		resumeButton.setEnabled(true);
	}
	
	private void deactivateSelectionListeners() {
	
		if (playerCards[selectionSlot].getStatus() != Card.STATUS_EMPTY
				&& playerCards[selectionSlot].getStatus() != Card.STATUS_DEAD) {
			playerCards[selectionSlot].setStatus(Card.STATUS_NORMAL);
		}
		for (Card card : opponentCards) {
			card.setOnClickListener(null);
		}
	}
	
	private void deactivateResumeListener() {
	
		resumeButton.setOnClickListener(null);
		resumeButton.setEnabled(false);
	}
	
	private void handleSendFailure(int code, Message msg) {
	
		switch (msg.arg1) {
			case WebSocketService.FAILURE_NO_MASTER:
				TCGLog.e(new Object[] {
					"Error sending a Message: Master has not been set!" });
				break;
			case WebSocketService.FAILURE_NOT_CONNECTED:
				TCGLog.e(new Object[] {
					"Error sending a Message: Not connected!" });
				break;
			case WebSocketService.FAILURE_UNKNOWN:
				TCGLog.e(new Object[] {
					"Error sending a Message: Unknown error!" });
				break;
			default:
				break;
		}
	}
	
	private void handleConnected() {
	
		TCGLog.d(new Object[] {
			"Handling connect." });
		waitingForConnectionDialog.dismiss();
		if (!joined && !gameOver) {
			String otp = getIntent().getStringExtra(EXTRA_OTP);
			long gameId = getIntent().getLongExtra(EXTRA_GAME_ID, 0);
			if (otp == null || "".equals(otp) || gameId <= 0) {
				throw new IllegalArgumentException("Extras are not correct!");
			}
			Message msg = Message.obtain(null, WebSocketService.MSG_JOIN);
			msg.obj = new Join(Example.LANGUAGE, gameId, Storage.getPlayerId(), otp);
			sendMessage(msg);
		}
	}
	
	private void handleDisconnected() {
	
		if (!gameOver) {
			TCGLog.d(new Object[] {
				"Handling disconnect." });
			waitingForConnectionDialog =
					ProgressDialog.show(this, "", "Connecting to Server. Please wait...", true);
		}
	}
	
	private void handleResumeRQ() {
	
		activateResumeListener();
		Toast.makeText(this, "Tap on 'Resume' to resume.", Toast.LENGTH_SHORT).show();
		TCGLog.d(new Object[] {
			"CONFIRM_RQ" });
	}
	
	private void handleSelectRQ(int slot) {
	
		selectionSlot = slot;
		activateSelectionListeners(slot);
		Toast.makeText(this, "Tap on an enemy card to attack it.", Toast.LENGTH_SHORT).show();
		TCGLog.d(new Object[] {
				"MSG_SELECTION_RQ: ", slot });
	}
	
	private void handleWaitRQ(int slot) {
		
		//TODO
		TCGLog.d(new Object[] {
				"MSG_WAIT_RQ: ", slot });
	}

	private void handleInfo(Info info) {
	
		Toast.makeText(this, info.getMsg(), Toast.LENGTH_SHORT).show();
		TCGLog.d(new Object[] {
				"MSG_INFO: ", info.getMsg() });
	}
	
	private void handleProgress(Progress progress) {
	
		Card card;
		if (progress.getPlayerId() == Storage.getPlayerId()) {
			card = playerCards[progress.getSlot()];
		}
		else {
			card = opponentCards[progress.getSlot()];
		}
		if (progress.getCardType() == -1) {
			card.setStatus(Card.STATUS_EMPTY);
		}
		else {
			updateCard(card, progress);
			if (progress.getLife() <= 0) {
				card.setStatus(Card.STATUS_DEAD);
			}
			else if (selectionRequired && selectionSlot == progress.getSlot()) {
				card.setStatus(Card.STATUS_TURN);
			}
			else {
				card.setStatus(Card.STATUS_NORMAL);
			}
		}
		TCGLog.d(new Object[] {
				"MSG_PROGRESS: ", progress.getSlot() });
	}
	
	private void updateCard(Card card, Progress progress) {
	
		CardData cardData;
		/* reading the card data */
		{
			card.updateValues(progress);
			int cardType = progress.getCardType();
			ContentObserver obs = new CardUpdater(getMainLooper(), contentAdapter, card, cardType);
			cardData = contentAdapter.readCardData(cardType, obs);
			card.updateData(cardData);
		}
		/* reading the properties */
		{
			/* all properties are categorized as added at first */
			List<Integer> addedProperties = new ArrayList<Integer>();
			for (Integer propertyType : progress.getProperties()) {
				if (propertyType != Example.PROPERTY_TYPE_DAMAGE
						&& propertyType != Example.PROPERTY_TYPE_ATTACKER) {// TODO
					addedProperties.add(propertyType);
				}
			}
			/* removing all base properties from the added properties and putting them in a separate
			 * list */
			int[] basePropertyArr = cardData.getPropertyTypes();
			TCGLog.d(new Object[] {
					"BASE PROPERTIES: ", cardData.getPropertyTypes().length });
			List<Integer> baseProperties = new ArrayList<Integer>();
			for (Integer propertyType : basePropertyArr) {
				if (addedProperties.contains(propertyType)) {
					baseProperties.add(propertyType);
					addedProperties.remove(new Integer(propertyType));
				}
				else {
					TCGLog.d(new Object[] {
							"A property has been removed from the ", basePropertyArr.length,
							" base properties! A Property of type ", propertyType,
							" is not longer present in the resent properties!" });
				}
			}
			/* setting the properties */
			card.clearProperties();
			for (Integer propertyType : baseProperties) {
				ContentObserver obs =
						new PropertyUpdater(getMainLooper(), contentAdapter, card, propertyType);
				card.addBaseProperty(propertyType, contentAdapter.readProperty(propertyType, obs));
			}
			for (Integer propertyType : addedProperties) {
				ContentObserver obs =
						new PropertyUpdater(getMainLooper(), contentAdapter, card, propertyType);
				card.addAddedProperty(propertyType, contentAdapter.readProperty(propertyType, obs));
			}
		}
	}
	
	private void handleJoined(int code) {
	
		Toast.makeText(this, "Joined a game!", Toast.LENGTH_SHORT).show();
		TCGLog.d(new Object[] {
				"MSG_JOINED: ", code });
	}
	
	private void handleWinner(int winner) {
	
		gameOver = true;
		if (winner == -1) {
			Toast.makeText(this, "Game Over. DRAW!", Toast.LENGTH_LONG).show();
		}
		// TODO Get the player id.
		else if (winner == Storage.getPlayerId()) {
			Toast.makeText(this, "Game Over. YOU WIN!", Toast.LENGTH_LONG).show();
		}
		else {
			Toast.makeText(this, "Game Over. YOU LOSE!", Toast.LENGTH_LONG).show();
		}
		TCGLog.d(new Object[] {
				"MSG_WINNER: ", winner });
	}
}